package com.pic.mgt.haishin.action;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import org.springframework.dao.DataAccessException;

import com.pic.mgt.IgxStatus;
import com.pic.mgt.action.ActionException;
import com.pic.mgt.action.BusinessRecoverAction;
import com.pic.mgt.dao.BasicBusinessTargetActionInfoBean;
import com.pic.mgt.dao.haishin.HaishinBasicBusinessTargetActionInfoBean;
import com.pic.mgt.dao.haishin.HaishinBusinessAsgmtActionInfoBean;
import com.pic.mgt.dao.haishin.HaishinBusinessMgtInfoBean;
import com.pic.mgt.dao.haishin.IAppointedHaishinBusinessAsgmtActionDao;
import com.pic.mgt.dao.haishin.IHaishinBusinessMgtDao;
import com.pic.mgt.dao.haishin.IHaishinBusinessTargetActionDao;
import com.pic.mgt.executer.SimpleConfirmationFileMaker;
import com.pic.mgt.file.io.FileOperation;
import com.pic.mgt.log.IgxLog;
import com.pic.mgt.monitor.BusinessUnitFactory;
import com.pic.mgt.monitor.parser.ConfirmtaionFileForTxFileParser;
import com.pic.mgt.utils.FilePathUtils;
import com.pic.mgt.utils.IgxFileUtils;

public class HaishinBusinessRecoverActionImpl implements
		BusinessRecoverAction {
	
	private String haishinInboxPathStr;
	private File haishinInboxPath;
	private String haishinOutboxPathStr;
	private File haishinOutboxPath;
	private IHaishinBusinessMgtDao haishinBusinessMgtDao;
	private IHaishinBusinessTargetActionDao haishinBusinessTargetActionDao;
	private IAppointedHaishinBusinessAsgmtActionDao appointedHaishinBusinessAsgmtActionDao;
	private FileOperation fileOperation;

	@Override
	public boolean recoverBusiness(String targetFilename) {
		if (null == targetFilename || targetFilename.isEmpty()) {
			// ********************** Log **********************
			IgxLog.messageLog.warn(this,
							"Recovering business failed. Did not provide target file name.");
			// ********************** Log **********************
			
			return false;
		}
		
		// ********************** Log **********************
		IgxLog.messageLog.info(this,
						"Starting recover business '{}' preprocess.",
						targetFilename);
		// ********************** Log **********************
		
		// 取得 target file
		File targetFile = new File(this.haishinInboxPath.getAbsolutePath(),
								   targetFilename);
		// 取得 target file 的確認檔
		File confirmationFile = ConfirmtaionFileForTxFileParser.getConfirmationFile(targetFile);
		
		// ----------------------- 以下開始處理 ------------------------------------
		
		
		try {
			// 先從資料庫中找出該業務記錄
			// throws DataAccessException
			HaishinBusinessMgtInfoBean haishinBusinessMgtInfoBean = null;
			try {
				haishinBusinessMgtInfoBean = this.haishinBusinessMgtDao.findHaishinBusinessMgtInfoByUnitName(targetFilename);
			} catch (DataAccessException e1) {
				// ********************** Log **********************
				IgxLog.messageDetailsLog.error(this, e1.getMessage());
				// ********************** Log **********************
				
				// ********************** Log **********************
				IgxLog.messageLog.error(this,
								"SUDA-211 Recovering business '{}' preprocess occured a database operation error.",
								targetFilename);
				// ********************** Log **********************
				
				throw new ActionException("");
			}
			
			if (null == haishinBusinessMgtInfoBean) {
				// 表示資料庫中沒有組入的記錄
				
				// 確認檔是否存在
				if (!confirmationFile.exists()) {
					// ********************** Log **********************
					IgxLog.messageLog.error(this,
									"SUDA-212 Can't not find confirmation file for business '{}'.",
									targetFile);
					// ********************** Log **********************
					return false;
				}
				
				// 重新讓確認檔案被偵測
				// throws ActionException
				redetectFile(targetFilename, confirmationFile);
			}
			else {
				// 表示資料庫中有組入的記錄
				
				if (haishinBusinessMgtInfoBean.getStatus().equals(IgxStatus.BUSINESS_COMPLETED)) {
					// 上次執行已經是成功的狀態，不需要做 recover
					
					// ********************** Log **********************
					IgxLog.messageLog.warn(this,
									"The last execution status of business '{}' was completive. No need to recover.",
									targetFilename);
					// ********************** Log **********************
					return true; // 回傳true不是false,因為不是發生錯誤.
				}
				
				/*
				 *  判斷業務是否有要做重配動作(僅指解 tar 的動作)的必要。因為如果上次的狀態已經確定是 BUSINESS_EXECUTING_FINISH，
				 *  表示"配的動作"已完成了，便不須再重做配的動作。
				 */
				// throws ActionException
				List<String> uncompletedTxFileList = checkIfBusinessNeedToRedo(haishinBusinessMgtInfoBean,
																			   targetFile,
																			   confirmationFile);
				if (null != uncompletedTxFileList) {
					// 表示需要重配
					
					// 但要先檢查 Target File 和 Confirmation File 是否都存在 (不存在根本沒辨法重配)
					if (!targetFile.exists() || !confirmationFile.exists()) {
						// ********************** Log **********************
						IgxLog.messageLog.error(this,
										"SUDA-213 Doing recover action for business '{}' failed. Can't find business file ('{}' or '{}') under haishin inbox.",
										haishinBusinessMgtInfoBean.getUnitName(),
										targetFile.getName(),
										confirmationFile.getName());
						// ********************** Log **********************
						
						throw new ActionException("");
					}
					
					if (uncompletedTxFileList.isEmpty()) {
						// throws ActionException
						updateHaishinBusinessStatusByUnitName(IgxStatus.BUSINESS_REDO,
															  haishinBusinessMgtInfoBean.getUnitName());
					}
					else {
						// throws ActionException
						updateHaishinBusinessStatusRedoAsginTerminalFileAndRedoAsginTerminalByUnitName(
																IgxStatus.BUSINESS_REDO,
																haishinBusinessMgtInfoBean.getRedoAsginTerminal(),
																covertUncompletedTxFileListToStringBySpace(uncompletedTxFileList),
																haishinBusinessMgtInfoBean.getUnitName());
					}
					
					// throws ActionException
					redetectFile(haishinBusinessMgtInfoBean.getUnitName(),
								 confirmationFile);
				}
				else {
					// 表示不需做重配的動作,但要去檢查後續的動作(移除業務檔和更新資料庫狀態)是否完成? 若沒有要完成.
					
					// 移除 target file
					if (targetFile.exists()) {
						// throws ActionException
						removeFile(haishinBusinessMgtInfoBean.getUnitName(), targetFile);
					}
					
					// 移除 confirmation file
					if (confirmationFile.exists()) {
						// throws ActionException
						removeFile(haishinBusinessMgtInfoBean.getUnitName(), confirmationFile);
					}
					
					// 更新資料庫為完成的狀態
					// throws ActionException
					updateHaishinBusinessStatusRedoAsginTerminalFileAndRedoAsginTerminalByUnitName(
							IgxStatus.BUSINESS_COMPLETED, null, null, haishinBusinessMgtInfoBean.getUnitName());
				}
			}
		} catch (ActionException e) {
			// ********************** Log **********************
				IgxLog.messageLog.error(this,
					"SUDA-214 Recovering business '{}' preprocess failed.",
					targetFilename);
			// ********************** Log **********************
			return false;
		}
		
		// ********************** Log **********************
		IgxLog.messageLog.info(this,
						"Recovering business '{}' preprocess completed.",
						targetFilename);
		// ********************** Log **********************
		
		return true;
	}
	
	/**
	 * 依照狀態判斷業務是否是需要進行重配的動作(僅指解 tar 的動作).
	 * @param haishinBusinessMgtInfoBean
	 * @return 回傳為 null 表示不需要進行重配的動作；回傳若為 List,但 list 內沒有 element表示需要做重配動作，但沒有指定要配哪些
	 *         檔案；若list 內有資料，要把要重配，同時指定要配哪些檔案。
	 */
	private List<String> checkIfBusinessNeedToRedo(HaishinBusinessMgtInfoBean haishinBusinessMgtInfoBean,
											  File targetFile,
											  File confirmationFile) throws ActionException {
		List<String> uncompletedTxFilenameList = new LinkedList<String>();
		
		String status = haishinBusinessMgtInfoBean.getStatus();
		
		if (status.equals(IgxStatus.BUSINESS_REDO) ||
			status.equals(IgxStatus.BUSINESS_LOADED) ||
			status.equals(IgxStatus.BUSINESS_SCHEDULED)) {
			// 該三種狀態都是要進行重配動作，並且沒有要配給那個檔案
			
			return uncompletedTxFilenameList;
		}
		else if (status.equals(IgxStatus.BUSINESS_EXECUTING)) {
			/* 上次狀態停留在執行中的情況:
			 * (1) 若上次是屬於 "指定重配給端末" 的動作，則要做進一步的判斷。
			 * (2) 若否，則直接做重配動作
			 */
			
			if (null == haishinBusinessMgtInfoBean.getRedoAsginTerminal()) {
				return uncompletedTxFilenameList;
			}
			else {
				// 表示上次是屬於 "指定重配給端末" 的動作(要做進一步的判斷)
				
				// 取得上次"指定重配給端末" 的動作還有哪些檔案沒做完
				// throws ActionException
				uncompletedTxFilenameList = getUncompletedTxFilenameListInLastResendToTerminalAction(
															haishinBusinessMgtInfoBean,
															haishinBusinessMgtInfoBean.getRedoAsginTerminal());
				if (null == uncompletedTxFilenameList) {
					// 表示都做完了，便不需要重配。
					
					// throws ActionException
					updateHaishinBusinessStatusByUnitName(IgxStatus.BUSINESS_EXECUTING_FINISH,
														  haishinBusinessMgtInfoBean.getUnitName());
					
					return null;
				}
				else {
					return uncompletedTxFilenameList;
				}
			}
		}
		else if (status.equals(IgxStatus.BUSINESS_FAILED)) {
			if (targetFile.exists() && confirmationFile .exists()) {
				// 二個檔案都存在,表示可能配信的動作還沒做完,要重配
				
				/*
				 * (1) 若上次是屬於 "指定重配給端末" 的動作，則要做進一步的判斷。
				 * (2) 若否，則直接做重配動作
				 */
				if (null == haishinBusinessMgtInfoBean.getRedoAsginTerminal()) {
					return uncompletedTxFilenameList;
				}
				else {
					// 表示上次是屬於 "指定重配給端末" 的動作(要做進一步的判斷)
					
					// 取得上次"指定重配給端末" 的動作還有哪些檔案沒做完
					// throws ActionException
					uncompletedTxFilenameList = getUncompletedTxFilenameListInLastResendToTerminalAction(
																haishinBusinessMgtInfoBean,
																haishinBusinessMgtInfoBean.getRedoAsginTerminal());
					if (null == uncompletedTxFilenameList) {
						// 表示都做完了，便不需要重配。
						
						// throws ActionException
						updateHaishinBusinessStatusByUnitName(IgxStatus.BUSINESS_EXECUTING_FINISH,
															  haishinBusinessMgtInfoBean.getUnitName());
						
						return null;
					}
					else {
						return uncompletedTxFilenameList;
					}
				}
			}
			else {
				// 表示配信的動作已有被做完,不需再做重配的動作
				
				// throws ActionException
				updateHaishinBusinessStatusByUnitName(IgxStatus.BUSINESS_EXECUTING_FINISH,
													  haishinBusinessMgtInfoBean.getUnitName());
				
				return null;
			}
		}
		else {
			/*
			 *  表示為 BUSINESS_EXECUTING_FINISH 
			 *  或 BUSINESS_COMPLETED(但不會有該狀態發生,若為COMPLETED則在前面會先被擋掉) 
			 */
			
			return null;
		}
	}
	
	/**
	 * 主要是針對上次的動作為 "指定重配給端末" 在未完成情況下，去檢查針對該端末上回的重配當中，
	 * 還有哪些檔案是還沒有配完的。
	 * @param haishinBusinessMgtInfoBean
	 * @param asignedTerminal
	 * @return 為 List 集合，內所包含的為字串。在 List 內的表示為還沒有配完的檔案；若為 null 表示沒有。
	 * @throws ActionException
	 */
	private List<String> getUncompletedTxFilenameListInLastResendToTerminalAction(
			HaishinBusinessMgtInfoBean haishinBusinessMgtInfoBean, String asginedTerminal) throws ActionException {
		
		File transactionFile;
		
		List<String> uncompletedTxFilenameList = new LinkedList<String>();
		
		String haishinType = haishinBusinessMgtInfoBean.getType();
		
		try {
			if (haishinType.equals(BusinessUnitFactory.APPINTED_HAISHIN)) {
				// 業務為指示配信
				
				// 取出 上次配的指示檔資訊 (只會有一筆)
				// throws DataAccessException
				HaishinBusinessAsgmtActionInfoBean haishinBusinessAsgmtActionInfoBean = 
												this.appointedHaishinBusinessAsgmtActionDao.
													findHaishinBasicBusinessAsgmtActionInfoByUnitNameAndTerminalId(
															haishinBusinessMgtInfoBean.getUnitName(), asginedTerminal);
				// 取得該交易檔
				transactionFile = FilePathUtils.multiPathConcat(this.haishinOutboxPath.getAbsolutePath(),
																asginedTerminal,
																haishinBusinessAsgmtActionInfoBean.getAsgmtFilename());
				
				// 判斷該指示檔上回是否產生完成
				// throws DataAccessException
				boolean isTxFileGeneratedCompletely = isTxFileGeneratedCompletely(transactionFile, haishinType);
				if (!isTxFileGeneratedCompletely) {
					uncompletedTxFilenameList.add(transactionFile.getName());
				}
			}
			else if (haishinType.equals(BusinessUnitFactory.GENERAL_HAISHIN)) {
				// 業務為一般配信
				
				// 取出上次target file 解tar出來的檔案清單資訊
				// throws DataAccessException
				List<BasicBusinessTargetActionInfoBean> basicBusinessTargetActionInfoBeanList =
					this.haishinBusinessTargetActionDao.findBasicBusinessTargetActionInfoListByUnitNameAndTerminalId(
							haishinBusinessMgtInfoBean.getUnitName(), asginedTerminal);
				
				for (BasicBusinessTargetActionInfoBean basicBusinessTargetActionInfoBean : basicBusinessTargetActionInfoBeanList) {
					// 取得該交易檔
					transactionFile = FilePathUtils.multiPathConcat(this.haishinOutboxPath.getAbsolutePath(),
																	asginedTerminal,
																	basicBusinessTargetActionInfoBean.getFilename());
					
					// 判斷該指示檔上回是否產生完成
					// throws DataAccessException
					boolean isTxFileGeneratedCompletely = isTxFileGeneratedCompletely(transactionFile, haishinType);
					if (!isTxFileGeneratedCompletely) {
						uncompletedTxFilenameList.add(transactionFile.getName());
					}
				}
			}
		} catch (DataAccessException e) {
			// ********************** Log **********************
			IgxLog.messageDetailsLog.error(this, e.getMessage());
			// ********************** Log **********************
			
			// ********************** Log **********************
			IgxLog.messageLog.error(this,
							"SUDA-211 Recovering business '{}' preprocess occured a database operation error.",
							haishinBusinessMgtInfoBean.getUnitName());
			// ********************** Log **********************
			
			throw new ActionException("");
		}
		
		if (uncompletedTxFilenameList.isEmpty()) {
			return null;
		}
		else {
			return uncompletedTxFilenameList;
		}
	}
	
	private boolean isTxFileGeneratedCompletely(File transactionFile, String haishinType) throws DataAccessException {
		// 取得該交易檔的確認檔
		File confirmationFile = ConfirmtaionFileForTxFileParser.getConfirmationFile(transactionFile);
		// 取得確認檔的暫存檔
		File confirmationTmpFile = SimpleConfirmationFileMaker.getTmepConfirmationFile(confirmationFile);
		
		/*
		 * 檔案沒配完的判斷準則:
		 * (1) 交易檔存在:
		 *     a. 暫存確認檔存在 -> 沒配完
		 *     b. 暫存確認檔和確認檔皆不存在:
		 *     	  [1] creating time 大於 last transaction time -> 沒配完
		 *        [2] last transaction time 為null -> 沒配完
		 * (2) 交易檔不存在:
		 *     a. 確認檔不存在
		 *        [1] creating time 大於 last transaction time -> 沒配完
		 *        [2] last transaction time 為null -> 沒配完
		 */
		
		if (transactionFile.exists()) {
			if (confirmationTmpFile.exists()) {
				return false;
			}
			else {
				if (!confirmationFile.exists()) {
					// 要再從資料庫中取得該交易檔的 creatimg time 和 last transaction time 一次去比較
					// throws DataAccessException
					Date[] dateInfo = getCreatingTimeAndLastTxTimeForTxFileFromDb(transactionFile.getName(), haishinType);
					Date creatingTime  = dateInfo[0];
					Date lastTransactionTime = dateInfo[1];
					
					if (null == lastTransactionTime) {
						return false;
					}
					else {
						if (creatingTime.after(lastTransactionTime)) {
							return false;
						}
					}
				}
			}
		}
		else {
			if (!confirmationFile.exists()) {
				// 要再從資料庫中取得該交易檔的 creatimg time 和 last transaction time 一次去比較
				// throws DataAccessException
				Date[] dateInfo = getCreatingTimeAndLastTxTimeForTxFileFromDb(transactionFile.getName(), haishinType);
				Date creatingTime  = dateInfo[0];
				Date lastTransactionTime = dateInfo[1];
				
				if (null == lastTransactionTime) {
					return false;
				}
				else {
					if (creatingTime.after(lastTransactionTime)) {
						return false;
					}
				}
			}
		}
		
		// 其它的狀況都代表該檔案有配完成
		return true;
	}
	
	/**
	 * 從資料庫中取得交易檔的 creating time 和 last transaction time 。
	 * @param transactionFilename
	 * @param haishinType
	 * @return [0]: creating time  [1]: last transaction time
	 * @throws DataAccessException
	 */
	private Date[] getCreatingTimeAndLastTxTimeForTxFileFromDb(String transactionFilename, String haishinType) throws DataAccessException {
		Date[] dateInfo = new Date[2];
		
		if (haishinType.equals(BusinessUnitFactory.APPINTED_HAISHIN)) {
			HaishinBusinessAsgmtActionInfoBean haishinBusinessAsgmtActionInfoBean =
				this.appointedHaishinBusinessAsgmtActionDao.findHaishinBasicBusinessAsgmtActionInfoByAsgmtFilename(transactionFilename);
			
			dateInfo[0] = haishinBusinessAsgmtActionInfoBean.getCreatingTime();
			dateInfo[1] = haishinBusinessAsgmtActionInfoBean.getLastTransactionTime();
		}
		else if (haishinType.equals(BusinessUnitFactory.GENERAL_HAISHIN)) {
			HaishinBasicBusinessTargetActionInfoBean haishinBasicBusinessTargetActionInfoBean =
				this.haishinBusinessTargetActionDao.findHaishinBasicBusinessTargetActionInfoByFilename(transactionFilename);
			
			dateInfo[0] = haishinBasicBusinessTargetActionInfoBean.getCreatingTime();
			dateInfo[1] = haishinBasicBusinessTargetActionInfoBean.getLastTransactionTime();
		}
		
		return dateInfo;
	}
	
	/**
	 * 更新資料庫 haishin business mgt table 的 status ,redo_asgin_terminal 和 redo_asgin_terminal_file 的欄位
	 * @param status
	 * @param redoAsginTerminal
	 * @param redoAsginTerminalFile
	 * @param unitName
	 * @throws ActionException
	 */
	private void updateHaishinBusinessStatusRedoAsginTerminalFileAndRedoAsginTerminalByUnitName(String status, String redoAsginTerminal,
																	       String redoAsginTerminalFile, String unitName)
	throws ActionException{
		// throws DataAccessException
		try {
			this.haishinBusinessMgtDao.updateStatusRedoAsginTerminalFileAndRedoAsginTerminalByUnitName(
														status, redoAsginTerminal, redoAsginTerminalFile, unitName);
		} catch (DataAccessException e) {
			// ********************** Log **********************
			IgxLog.messageDetailsLog.error(this, e.getMessage());
			// ********************** Log **********************
			
			// ********************** Log **********************
			IgxLog.messageLog.error(this,
					"SUDA-211 Recovering business '{}' preprocess occured a database operation error.",
							unitName);
			// ********************** Log **********************
			
			throw new ActionException("");
		}
	}
	
	private void updateHaishinBusinessStatusByUnitName(String status, String unitName)
	throws ActionException {
		// throws DataAccessException
		try {
			this.haishinBusinessMgtDao.updateStatusByUnitName(status, unitName);
		} catch (DataAccessException e) {
			// ********************** Log **********************
			IgxLog.messageDetailsLog.error(this, e.getMessage());
			// ********************** Log **********************
			
			// ********************** Log **********************
			IgxLog.messageLog.error(this,
					"SUDA-211 Recovering business '{}' preprocess occured a database operation error.",
							unitName);
			// ********************** Log **********************
			
			throw new ActionException("");
		}
	}
	
	private void removeFile(String businessName, File toBeRemovedFile) throws ActionException {
		try {
			this.fileOperation.deleteFile(toBeRemovedFile);
		} catch (IOException e) {
			// ********************** Log **********************
			IgxLog.messageDetailsLog.error(this, e.getMessage());
			// ********************** Log **********************
			
			// ********************** Log **********************
			IgxLog.messageLog.error(this,
					"SUDA-215 Recovering business '{}' preprocess occured an I/O error in removeing file '{}'.",
					businessName, toBeRemovedFile.getAbsolutePath());
			// ********************** Log **********************
			
			throw new ActionException("");
		}
	}
	
	/**
	 * 重新偵測檔案
	 * @param businessName
	 * @param redetectFile
	 * @throws ActionException
	 */
	private void redetectFile(String businessName, File redetectFile) throws ActionException {
		try {
			// throws IOException
			IgxFileUtils.redetectFile(redetectFile);
		} catch (IOException e) {
			// ********************** Log **********************
			IgxLog.messageDetailsLog.error(this, e.getMessage());
			// ********************** Log **********************
			
			// ********************** Log **********************
			IgxLog.messageLog.error(this,
							"SUDA-216 Recovering business'{}' preprocess occured an I/O error in redetecting confirmation file '{}' step.",
							businessName, redetectFile.getAbsolutePath());
			// ********************** Log **********************
			
			throw new ActionException("");
		}
	}
	
	private String covertUncompletedTxFileListToStringBySpace(List<String> uncompletedTxFileList) {
		StringBuffer strBuf = new StringBuffer();
		
		for (String txFilename : uncompletedTxFileList){
			strBuf.append(txFilename);
			strBuf.append(" ");
		}
		
		return strBuf.toString().trim();
	}
	
	public void setHaishinInboxPathStr(String haishinInboxPathStr) {
		this.haishinInboxPathStr = haishinInboxPathStr;
		this.haishinInboxPath = new File(haishinInboxPathStr);
	}

	public void setHaishinBusinessMgtDao(
			IHaishinBusinessMgtDao haishinBusinessMgtDao) {
		this.haishinBusinessMgtDao = haishinBusinessMgtDao;
	}

	public void setFileOperation(FileOperation fileOperation) {
		this.fileOperation = fileOperation;
	}

	public void setHaishinBusinessTargetActionDao(
			IHaishinBusinessTargetActionDao haishinBusinessTargetActionDao) {
		this.haishinBusinessTargetActionDao = haishinBusinessTargetActionDao;
	}

	public void setAppointedHaishinBusinessAsgmtActionDao(
			IAppointedHaishinBusinessAsgmtActionDao appointedHaishinBusinessAsgmtActionDao) {
		this.appointedHaishinBusinessAsgmtActionDao = appointedHaishinBusinessAsgmtActionDao;
	}

	public void setHaishinOutboxPathStr(String haishinOutboxPathStr) {
		this.haishinOutboxPathStr = haishinOutboxPathStr;
		this.haishinOutboxPath = new File(haishinOutboxPathStr);
	}

}
